/*
 * Copyright (c) 2010 Vadim Zhukov <persgray@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#include "common.h"

#include <sys/param.h>
#include <sys/un.h>
#include <sys/wait.h>

#include <netinet/in.h>

#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fnmatch.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>

#include "logmsg.h"
#include "sensor.h"
#include "syslogd.h"


static struct sl_context	slc;
static struct logbufferhead	reqbuffers;
static int			syslog_refs;


int
sensor_syslog_bsd_init(struct wsensor *s) {
	struct logbuffer	*lb = NULL;
	int			 oerrno, rv = -1;

	if (syslog_refs > 0 && SLIST_FIRST(&reqbuffers) == NULL) {
		DPRINTF(1, "already listening all syslog buffers");
		errno = EBUSY;
		return -1;
	}
	if (*s->params == '\0') {
		if (syslog_refs > 0) {
			DPRINTF(1, "already listening on some syslog buffers");
			errno = EBUSY;
			return -1;
		}
	} else {
		lb = malloc(sizeof(struct logbuffer));
		if (lb == NULL)
			return -1;
		bzero(lb, sizeof(struct logbuffer));
		if (strlcpy(lb->lb_name, s->params, sizeof(lb->lb_name)) >=
		    sizeof(lb->lb_name)) {
			errno = EINVAL;
			goto err;
		}
		SLIST_INSERT_HEAD(&reqbuffers, lb, lb_entry);
	}

	if (syslog_refs == 0)
		rv = sl_init_context(&slc, &reqbuffers);
	s->fd = slc.logsock;
	if (rv == -1)
		goto err;
	if (sl_update_buffers_list(&slc) == -1)
		goto err;
	syslog_refs++;
	return 0;

err:
	oerrno = errno;
	free(lb);
	errno = oerrno;
	return -1;
}

int
sensor_syslog_bsd_uninit(struct wsensor *s) {
	assert(syslog_refs > 0);
	if (--syslog_refs == 0)
		sl_deinit_context(&slc);
	return 0;
}

#define s_cache       s->sp.sp_syslog_bsd.cache
#define s_cache_pos   s->sp.sp_syslog_bsd.cache_pos
#define s_cache_size  s->sp.sp_syslog_bsd.cache_size

int
sensor_syslog_bsd_get(struct wsensor *s, struct log_msg *m) {
	struct sl_task	*task;
	size_t		 i;
	int		 rv;

	DPRINTFX(5, "sensor_syslog_bsd_get()");

	if (sl_proceed_tasks(&slc) == -1)
		DPRINTF(1, "sl_proceed_tasks");
	s->fd = slc.logsock;
	if (s_cache != NULL && s_cache_pos == s_cache_size) {
		free(s_cache);
		s_cache = NULL;
		s_cache_size = 0;
	}
	if (s_cache == NULL) {
		if (sl_read_and_clear(&slc))
			DPRINTF(1, "sl_read_and_clear");
		s->fd = slc.logsock;
		while ((task = sl_pick_done_task(&slc)) != NULL) {
			if (sl_task_failed(task)) {
				errno = task->error;
				DPRINTF(1, "syslogd task failed");
			} else if ((sl_task_code(task) == SL_CMD_READ_CLEAR ||
			    sl_task_code(task) == SL_CMD_READ) &&
			    sl_task_res_size(task) > 0) {
				s_cache = task->result;
				task->result = NULL;    /* XXX */
				s_cache_size = sl_task_res_size(task);
				s_cache_pos = 0;
				break;
			}
			sl_free_task(task);
		}
	}
	if (s_cache == NULL) {
		errno = EAGAIN;
		return -1;
	}
	for (i = s_cache_pos; i < s_cache_size; i++)
		if (s_cache[i] == '\n')
			break;
	rv = sensor_convert_log_line(s_cache + s_cache_pos, i - s_cache_pos, m);
	/* Do not try to convert failed line twice */
	if (i < s_cache_size)
		i++;
	s_cache_pos = i;
	return rv;
}
